前一篇講那麼久,都是輸出型別檔後,再手動複製型別檔到其他專案內,或是輸出的檔名固定是 sanity.types.ts
。
但是不管是輸出路徑還是檔名,都是可以修改的。
只要在 Sanity 專案內建立 sanity-typegen.json
,並且修改裡面的設定就可以了。
{
"path": "./src/**/*.{ts,tsx,js,jsx}",
"schema": "schema.json",
"generates": "./sanity.types.ts", // <- 輸出路徑跟檔名
"overloadClientMethods": true
}
只要修改 generates
就可以定義要輸出的型別檔位置了,
.
├── next-app
│ ├── app
│ │ └── sanity
└── sanity-project
├── sanity-typegen.json // <- Sanity 輸出設定檔
├── sanity.types.ts
├── schema.json
舉例來說,我現在要把從 sanity-project 專案內生成出來的型別檔輸出到 next-app 內,並且存在 /next-app/app/sanity/
資料夾下,檔名為 types.ts
我可以在 sanity-typegen.json
內這樣改:
{
"path": "./src/**/*.{ts,tsx,js,jsx}",
"schema": "schema.json",
"generates": "../next-app/app/sanity/types.ts", // 輸出路徑跟檔名
"overloadClientMethods": true
}
這樣就完成設定了!
使用上可以這樣使用:
next-app
├── app
│ ├── page.tsx
│ └── sanity
│ └── types.ts // <- 輸出的型別檔
// page.tsx 使用範例
import { client } from "@/app/sanity/lib/client";
import { BLOG_POSTS_QUERY } from "@/app/sanity/lib/queries";
import type { BlogPost } from "./sanity/types";
export default async function Home() {
const posts = await client.fetch<BlogPost[]>(BLOG_POSTS_QUERY);
return (
<ul className="home-page">
{/* 型別就沒錯誤了 */}
{posts.map((post) => (
<li key={post._id}>
<a href={`/posts/${post?.slug}`}>{post?.title}</a>
</li>
))}
</ul>
);
}
但是先別急著拿去用,還有再更進階的,產生的型別甚至可以根據 defineQuery
包裹的 GROQ 語法去自動產生語法型別的回傳。
什麼意思?
意思是 sanity typegen 會根據
import { defineQuery } from "next-sanity";
export const BLOG_POSTS_QUERY = defineQuery(`*[_type == "blogPost"]`);
自動在使用 sanity client 呼叫語法時回傳相對應的型別。
實際要讓這個 feature 生效,則是要確保在 sanity-typegen.json
中的 overloadClientMethods
為 true
,並且定義的 path 有包含到 defineQuery
的的檔案。
回到我們的案例:
.
├── next-app
│ ├── app
│ │ └── sanity
│ │ ├── lib // <- query 語法都寫在這
│ │ └── types.ts
└── sanity-project
├── sanity-typegen.json
我們的案例是,所有 Next.js 專案的 GROQ 語法都會寫在 next-app/app/sanity/lib
內,並且都是以 .ts
檔案的形式,所以我可以修改 sanity-typegen.json
中的 path 為:
{
"path": "../next-app/app/sanity/lib/*.ts", // <- 專案內 "defineQuery" 都集中在這管理
"schema": "schema.json",
"generates": "../next-app/app/sanity/types.ts",
"overloadClientMethods": true // <- 此欄位一定要是 true,才會有這項 feature
}
設定完成後再使用指令產生型別檔就完成了。
( 因為沒有動到 schema 結構,就不用再產生一次 schema 了 )
sanity typegen generate
這麼一來在使用 client
使用 fetch()
方法時,型別就會自動推斷而不用再去引入了:
import { client } from "@/app/sanity/lib/client";
import { BLOG_POSTS_QUERY } from "@/app/sanity/lib/queries";
export default async function Home() {
const posts = await client.fetch(BLOG_POSTS_QUERY);
return (
<ul className="home-page">
{posts.map((post) => (
<li key={post._id}>
<a href={`/posts/${post?.slug}`}>{post?.title}</a>
</li>
))}
</ul>
);
}
連型別都不用引入了,直接就有型別的推斷了!很舒服吧~~
那在 type.ts
( 舊稱 sanity.types.ts
) 型別檔根據此參數產生的細節在這裡,看看就不特別解釋這段了:
// Source: ../next-app/app/sanity/lib/queries.ts
// Variable: BLOG_POSTS_QUERY
// Query: *[_type == "blogPost"]
export type BLOG_POSTS_QUERYResult = Array<{
_id: string;
_type: "blogPost";
_createdAt: string;
_updatedAt: string;
_rev: string;
title: string;
slug: string;
subtitle?: string;
heroImage: {
asset?: {
_ref: string;
_type: "reference";
_weak?: boolean;
[internalGroqTypeReferenceTo]?: "sanity.imageAsset";
};
hotspot?: SanityImageHotspot;
crop?: SanityImageCrop;
_type: "image";
};
content: string;
publishedAt: string;
tags?: Array<string>;
}>;
// Query TypeMap
import "@sanity/client";
declare module "@sanity/client" {
interface SanityQueries {
"*[_type == \"blogPost\"]": BLOG_POSTS_QUERYResult;
}
}